package com.nervenets.general.web;

import com.nervenets.general.annotation.FormDefinition;
import com.nervenets.general.annotation.ProhibitDuplicateRequest;
import com.nervenets.general.aspect.SysLog;
import com.nervenets.general.entity.*;
import com.nervenets.general.enumeration.Action;
import com.nervenets.general.exception.LogicException;
import com.nervenets.general.hibernate.DomainObject;
import com.nervenets.general.jwt.aspect.JwtSecurity;
import com.nervenets.general.service.BaseService;
import com.nervenets.general.utils.ClassUtils;
import com.nervenets.general.utils.JodaUtils;
import com.nervenets.general.utils.SpringContextHolder;
import com.nervenets.general.utils.StringUtils;
import com.nervenets.general.web.params.EntityPagingParams;
import com.nervenets.general.web.params.FormParams;
import com.nervenets.general.web.params.IdParams;
import com.nervenets.general.web.params.IdsParams;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.nervenets.general.i18n.I18nTranslator.translate;

/**
 * 实体基础 BaseEntityController
 *
 * @param <T>  实体对象
 * @param <S>  实体服务对象
 * @param <F>  实体表单参数对象
 * @param <P>  实体分页参数对象
 * @param <LR> 列表返回实体对象
 * @param <DR> 详情返回实体对象
 */
@Slf4j
public abstract class BaseEntityController<
        T extends DomainObject,
        S extends BaseService<T>,
        F extends FormParams<T, S>,
        P extends EntityPagingParams<T>, LR, DR>
        extends BaseController {
    @Autowired
    public S service;

    /**
     * action操作之前的功能及拦截，默认什么都不做
     *
     * @param action 操作类型
     */
    protected void before(Action action, F params, HttpServletRequest request, HttpServletResponse response) {
        //nothing to do ...
    }

    /**
     * action操作之后的功能及拦截，默认什么都不做
     *
     * @param action 操作类型
     * @param t      操作对象
     */
    protected void after(Action action, T t, F params, HttpServletRequest request, HttpServletResponse response) {
        //nothing to do ...
    }

    /**
     * 构建列表返回对象，默认返回源对象
     *
     * @param t 源对象
     * @return 返回对象
     */
    protected LR generateListResult(T t, P params, HttpServletRequest request, HttpServletResponse response) {
        return (LR) t;
    }

    /**
     * 构建列表返回列表对象，默认返回源对象
     *
     * @param page   源对象列表
     * @param params
     * @return 返回对象列表
     */
    protected List<LR> generateListResults(Page<T> page, P params, HttpServletRequest request, HttpServletResponse response) {
        return page.get().map(t -> generateListResult(t, params, request, response)).collect(Collectors.toList());
    }

    /**
     * 构建详情返回对象，默认返回源对象
     *
     * @param t 源对象
     * @return 返回对象
     */
    protected DR generateDetailResult(T t, HttpServletRequest request, HttpServletResponse response) {
        return (DR) t;
    }

    /**
     * 构建列表返回附加对象，默认返回null
     *
     * @param params   参数
     * @param request  request
     * @param response response
     * @return
     */
    protected Map<String, ?> generateListExtra(P params, Page<T> page, HttpServletRequest request, HttpServletResponse response) {
        return null;
    }

    /**
     * 构建详情返回附加对象，默认返回null
     *
     * @param t        对象
     * @param request  request
     * @param response response
     * @return
     */
    protected Map<String, ?> generateDetailExtra(T t, HttpServletRequest request, HttpServletResponse response) {
        return null;
    }

    protected Map<String, ?> generateDetailsExtra(List<T> ts, HttpServletRequest request, HttpServletResponse response) {
        return null;
    }

    @SysLog
    @ApiOperation(value = "列表")
    @PostMapping("/list")
    @JwtSecurity(permission = "list", permissionName = "{application.permission.name.list}", queue = 50)
    public ResponseEntity<ResponsePageResult<LR>> list(@ApiParam(name = "params", value = "参数实体", required = true) @Valid @RequestBody P params, HttpServletRequest request, HttpServletResponse response) {
        setJsonIgnoreEnv("list", request);
        Page<T> page = service.findAll(params, PageRequest.of(params.getPage(), params.getLimit()));
        return successMessage(generateListResults(page, params, request, response), params.getPage(), page.getTotalElements(), generateListExtra(params, page, request, response));
    }

    @ApiOperation(value = "获取对象字段")
    @GetMapping("/fields")
    public ResponseEntity<ResponseResult<EntityConstructor>> fields() {
        Class<F> formEntityClass;
        Class<LR> listEntityClass;
        try {
            final Type[] arguments = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
            formEntityClass = (Class<F>) arguments[2];
            listEntityClass = (Class<LR>) arguments[arguments.length - 2];
        } catch (Exception e) {
            throw new LogicException(translate("application.exception.field.get.error.messages"), e);
        }
        return successMessage(EntityConstructor.builder()
                .forms(analysisFormFields(formEntityClass))
                .lists(analysisFormFields(listEntityClass).stream().map(FormBaseField::new).collect(Collectors.toList()))
                .build());
    }

    private List<FormField> analysisFormFields(Class<?> entityClass) {
        List<FormField> ruleFields = new ArrayList<>();
        final Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(FormDefinition.class) || field.isAnnotationPresent(ApiModelProperty.class)) {
                final String fieldName = field.getName();
                final FormDefinition description = field.getAnnotation(FormDefinition.class);
                final ApiModelProperty property = field.getAnnotation(ApiModelProperty.class);

                final String name = null != description ? description.value() : property.value();
                final String desc = null != description ? description.desc() : property.notes();
                final boolean required = null != description ? description.required() : property.required();
                final String group = null != description ? description.group() : null;
                final String def = null != description ? description.def() : null;
                final int maxLength = null != description ? description.maxLength() : 0;

                List<Selection> selections = null;
                final FormDefinition.InputType inputType = null != description ? description.inputType() : FormDefinition.InputType.customOf(property.dataType());
                if (null != description && FormDefinition.InputType.selectionTypes().contains(inputType)) {
                    final Class<? extends BaseService> service = description.selectionService();
                    final String selectionMethod = description.selectionMethod();
                    if (BaseService.class.equals(service) || StringUtils.isBlank(selectionMethod)) {
                        throw new LogicException(String.format("请配置%s的选项获取方法", name));
                    }

                    selections = (List<Selection>) ClassUtils.callMethod(SpringContextHolder.getBean(service), selectionMethod);
                }

                final FormField formField = FormField.builder()
                        .key(fieldName)
                        .name(name)
                        .desc(desc)
                        .required(required)
                        .inputType(inputType)
                        .group(group)
                        .def(def)
                        .maxLength(maxLength)
                        .selections(selections)
                        .build();

                if (FormDefinition.InputType.object.equals(inputType)) {
                    formField.setChildren(analysisFormFields(field.getType()));
                }

                ruleFields.add(formField);
            }
        }
        return ruleFields;
    }

    @SysLog
    @ApiOperation(value = "编辑")
    @PostMapping("/edit")
    @JwtSecurity(permission = "edit", permissionName = "{application.permission.name.edit}", queue = 200)
    @ProhibitDuplicateRequest
    public ResponseEntity<ResponseResult<DR>> edit(@ApiParam(name = "params", value = "参数实体", required = true) @Valid @RequestBody F params, HttpServletRequest request, HttpServletResponse response) {
        boolean isNew = params.getFormId() == 0;
        before(isNew ? Action.add : Action.update, params, request, response);
        T t = service.formSave(params, request, response);
        after(isNew ? Action.add : Action.update, t, params, request, response);
        return successMessage(generateDetailResult(t, request, response), generateDetailExtra(t, request, response));
    }

    @SysLog
    @ApiOperation(value = "获取表单数据")
    @GetMapping("/form/{id}")
    public ResponseEntity<ResponseResult<F>> formData(@PathVariable long id) {
        T t = service.findOne(id);
        if (null == t || t.isDeleted()) throw new LogicException(404, "您要查看的数据不存在");
        Class<F> formEntityClass;
        try {
            final Type[] arguments = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
            formEntityClass = (Class<F>) arguments[2];
            final F f = formEntityClass.newInstance();
            f.convert(t);
            return successMessage(f);
        } catch (Exception e) {
            throw new LogicException("获取表单详情错误", e);
        }
    }

    @SysLog
    @ApiOperation(value = "删除")
    @PostMapping("/del")
    @JwtSecurity(permission = "del", permissionName = "{application.permission.name.delete}", queue = 1000)
    @ProhibitDuplicateRequest
    public ResponseEntity<ResponseResult<Void>> del(@ApiParam(name = "params", value = "参数实体", required = true) @Valid @RequestBody IdsParams params, HttpServletRequest request, HttpServletResponse response) throws InstantiationException, IllegalAccessException {
        Class<F> formEntityClass;
        Class<T> entityClass;
        final Type[] arguments = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
        entityClass = (Class<T>) arguments[0];
        formEntityClass = (Class<F>) arguments[2];

        HashMap<Long, F> formParamsMap = new HashMap<>();

        List<T> ts = new ArrayList<>();
        for (Long targetId : params.getTargetIds()) {
            final F f = formEntityClass.newInstance();
            T t = service.findOne(targetId, String.format("您要删除的数据[%s]不存在", targetId));
            f.bindOld(entityClass, t);
            if (!t.isDeleted()) {
                t.setDeleted(JodaUtils.getTimestamp());
                f.setFormId(t.getId());
                f.bindNow(entityClass, t);
                formParamsMap.put(targetId, f);
                before(Action.delete, f, request, response);
                ts.add(t);
            }
        }
        service.saveAll(ts, params);
        ts.forEach(t -> after(Action.delete, t, formParamsMap.get(t.getId()), request, response));
        return successMessage();
    }

    @SysLog
    @ApiOperation(value = "详情")
    @PostMapping("/detail")
    @JwtSecurity(permission = "detail", permissionName = "{application.permission.name.detail}", queue = 100)
    public ResponseEntity<ResponseResult<DR>> detail(@ApiParam(name = "params", value = "参数实体", required = true) @Valid @RequestBody IdParams params, HttpServletRequest request, HttpServletResponse response) {
        setJsonIgnoreEnv("detail", request);
        T t = service.findOne(params.getTargetId());
        if (null == t || t.isDeleted()) throw new LogicException(404, "您要查看的数据不存在");
        return successMessage(generateDetailResult(t, request, response), generateDetailExtra(t, request, response));
    }

    @SysLog
    @ApiOperation(value = "查看多个详情")
    @PostMapping("/details")
    public ResponseEntity<ResponseResult<List<DR>>> details(@ApiParam(name = "params", value = "参数实体", required = true) @Valid @RequestBody IdsParams params, HttpServletRequest request, HttpServletResponse response) {
        setJsonIgnoreEnv("detail", request);
        final List<T> ts = service.findAllByIds(params.getTargetIds());
        return successMessage(ts
                .stream()
                .filter(t -> !(null == t || t.isDeleted()))
                .map(t -> generateDetailResult(t, request, response))
                .collect(Collectors.toList()), generateDetailsExtra(ts, request, response));
    }
}
